package com.onepiece.metrics;

import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.Tags;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.config.MeterFilter;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.prometheus.PrometheusMeterRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import javax.annotation.PostConstruct;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

/**
 * 统一封装PrometheusMeterRegistry操作
 *
 * @author guoyanjiang
 * @date 2019-05-08
 */
@Service
public class MetricsServiceImpl implements MetricsService {

    @Autowired
    PrometheusMeterRegistry prometheus;

    ConcurrentHashMap<String, Counter> counterMap = new ConcurrentHashMap(1024);
    ConcurrentHashMap<String, Timer> timerMap = new ConcurrentHashMap(4096);
    ConcurrentHashMap<String, Double> gaugeValueMap = new ConcurrentHashMap(1024);

    @PostConstruct
    public void init() {
        prometheus.config().meterFilter(new MeterFilter() {
            @Override
            public DistributionStatisticConfig configure(Meter.Id id, DistributionStatisticConfig config) {
                return DistributionStatisticConfig.builder()
                        .percentiles(0.5, 0.75, 0.9, 0.95, 0.99)
                        .build()
                        .merge(config);
            }
        });
    }

    @Override
    public void increment(CustomMetricsEnum metricsEnum, Map<String, String> tags) {
        increment(metricsEnum, convertMapToTags(tags));
    }

    @Override
    public void increment(String name, Map<String, String> tags, double amount) {
        checkoutCounter(name, convertMapToTags(tags)).increment(amount);
    }

    @Override
    public void increment(CustomMetricsEnum metricsEnum, String... tags) {
        checkoutCounter(metricsEnum.name, tags).increment();
    }

    private Counter checkoutCounter(String name, String... tags) {
        String key = providePrometheusKey(name, tags);
        Counter counter = counterMap.get(key);
        if (counter == null) {
            //同步问题先忽略，先看表现
            counter = prometheus.counter(name, tags);
            counterMap.put(key, counter);
        }
        return counter;
    }

    @Override
    public <T> T record(CustomMetricsEnum metricsEnum, Supplier<T> f, Map<String, String> tags) {
        return record(metricsEnum, f, convertMapToTags(tags));
    }

    @Override
    public <T> T record(CustomMetricsEnum metricsEnum, Supplier<T> f, String... tags) {
        return checkoutTimer(metricsEnum.name, tags).record(f);
    }

    @Override
    public void record(CustomMetricsEnum metricsEnum, Runnable f, Map<String, String> tags) {
        record(metricsEnum, f, convertMapToTags(tags));
    }

    @Override
    public void record(CustomMetricsEnum metricsEnum, Runnable f, String... tags) {
        checkoutTimer(metricsEnum.name, tags).record(f);
    }

    @Override
    public void record(String metric, long timeAmount, TimeUnit timeUnit, Map<String, String> tags) {
        checkoutTimer(metric, convertMapToTags(tags)).record(timeAmount, timeUnit);
    }

    @Override
    public void record(CustomMetricsEnum metricsEnum, long timeAmount, TimeUnit timeUnit, Map<String, String> tags) {
        record(metricsEnum, timeAmount, timeUnit, convertMapToTags(tags));
    }

    @Override
    public void record(CustomMetricsEnum metricsEnum, long timeAmount, TimeUnit timeUnit, String... tags) {
        checkoutTimer(metricsEnum.name, tags).record(timeAmount, timeUnit);
    }

    private Timer checkoutTimer(String name, String... tags) {
        String key = providePrometheusKey(name, tags);
        Timer timer = timerMap.get(key);
        if (timer == null) {
            //同步问题先忽略，先看表现
            timer = prometheus.timer(name, tags);
            timerMap.put(key, timer);
        }
        return timer;
    }

    @Override
    public void gauge(String metric, double num, String... tags) {
        String key = providePrometheusKey(metric, tags);
        Double latencySecond = gaugeValueMap.get(key);
        if (latencySecond == null) {
            //map取出为空，表示没有注册仪表盘
            gaugeValueMap.put(key, num);
            prometheus.gauge(metric, Tags.of(tags), gaugeValueMap, map -> map.get(key));
        } else {
            //取出不为空，说明已注册，只要更新这个值即可被观察到
            gaugeValueMap.put(key, num);
        }
    }

    @Override
    public void gauge(String metric, double num, Map<String, String> tags) {
        gauge(metric, num, convertMapToTags(tags));
    }

    @Override
    public void gauge(CustomMetricsEnum metricsEnum, double num, Map<String, String> tags) {
        gauge(metricsEnum.name, num, convertMapToTags(tags));
    }

    /**
     * 使用自建容器来注册、更新仪表盘
     * @param metricsEnum
     * @param num
     * @param tags
     */
    @Override
    public void gauge(CustomMetricsEnum metricsEnum, double num, String... tags) {
        gauge(metricsEnum.name, num, tags);
    }

    private String[] convertMapToTags(Map<String, String> tagsMap) {
        if (tagsMap != null) {
            String[] tags = new String[tagsMap.size() * 2];
            int index = 0;
            for (Map.Entry<String, String> entry : tagsMap.entrySet()) {
                tags[index] = entry.getKey();
                index++;
                tags[index] = entry.getValue();
                index++;
            }
            return tags;
        }
        return null;
    }

    private String providePrometheusKey(String name, String... tags) {
        if (tags.length > 1) {
            return String.format("%s_%s", name, String.join("_", tags));
        }
        return name;
    }
}